﻿/*
** Xin YUAN, 2021, BSD (2)
*/

////////////////////////////////////////////////////////////////////////////////

// server application

typedef void (*server_cleanup_proc)(void*);

static
server_cleanup_proc g_cleanup_proc = NULL;
static
void* g_cleanup_param = NULL;

static
volatile DWORD g_dwMainThreadId = 0;

static
inline void _post_quit_message() noexcept
{
	::PostThreadMessageW(g_dwMainThreadId, WM_QUIT, 0, 0);  //no check
}

//This function will be executed in a new thread. (a remote thread invoked by system process)
static
BOOL WINAPI _ctrl_handler(DWORD dwCtrlType)
{
	BOOL bRet = FALSE;
	switch(dwCtrlType) {
	case CTRL_C_EVENT:
	case CTRL_BREAK_EVENT:
	case CTRL_CLOSE_EVENT:
		_post_quit_message();
		bRet = TRUE;
		break;
	case CTRL_LOGOFF_EVENT: //only service
	case CTRL_SHUTDOWN_EVENT: //only service
		_post_quit_message();
		bRet = TRUE;
		break;
	default:
		break;
	}
	return bRet;
}

static
constexpr LPCWSTR c_szCtrlWndClassName = L"CtrlHandlerWndClass";

static
HWND g_hCtrlWindow = NULL;

LRESULT CALLBACK _ctrl_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	LRESULT lRes = 0;
	switch(uMsg) {
	case WM_QUERYENDSESSION:
		//lParam: 0 --- shutdown
		//        bit mask --- ENDSESSION_CLOSEAPP ENDSESSION_CRITICAL ENDSESSION_LOGOFF
		//If the time of cleanup > 30s, use ::ShutdownBlockReasonCreate()
		lRes = TRUE;
		break;
	case WM_ENDSESSION:
		//lParam: 0 --- shutdown
		//        bit mask --- ENDSESSION_CLOSEAPP ENDSESSION_CRITICAL ENDSESSION_LOGOFF
		if ( wParam ) //clean up
			g_cleanup_proc(g_cleanup_param);
		else
			lRes = 1;
		break;
	default:
		lRes = ::DefWindowProcW(hwnd, uMsg, wParam, lParam);
		break;
	}
	return lRes;
}

//initialize
bool initialize_server_application(void (*cleanup_proc)(void*), void* param) noexcept
{
	g_cleanup_proc = cleanup_proc;
	g_cleanup_param = param;
	g_dwMainThreadId = ::GetCurrentThreadId();
	//message queue
	MSG msg;
	::PeekMessageW(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);  //no check
	//hidden window
	WNDCLASSEXW wcCtrl{ 0 };
	wcCtrl.cbSize = sizeof(WNDCLASSEXW);
	wcCtrl.lpfnWndProc = &_ctrl_window_proc;
	wcCtrl.lpszClassName = c_szCtrlWndClassName;
	ATOM wcAtom = ::RegisterClassExW(&wcCtrl);
	if ( wcAtom == 0 ) { //::GetLastError
		::perror("ERROR: Could not register window class!");
		return false;
	}
	g_hCtrlWindow = ::CreateWindowExW(0, MAKEINTATOM(wcAtom), L"Console Control Handler Window", 0,
									CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
									NULL, NULL, NULL, NULL);
	if ( g_hCtrlWindow == NULL ) { //::GetLastError
		::perror("ERROR: Could not create window!");
		return false;
	}
	::ShowWindow(g_hCtrlWindow, SW_HIDE);  //no check
	//console control handler
	if ( !::SetConsoleCtrlHandler(&_ctrl_handler, TRUE) ) { //::GetLastError
		::DestroyWindow(g_hCtrlWindow);  //no check
		g_hCtrlWindow = NULL;
		::perror("ERROR: Could not set control handler!");
		return false;
	}
	return true;
}

//loop
int32_t run_server_loop() noexcept
{
	// Pump message loop for the window.
	BOOL bRet;
	MSG msg;
	while ( (bRet = ::GetMessageW(&msg, NULL, 0, 0)) != 0 ) { //may block
		if ( bRet == -1 ) //error
			break;
		::TranslateMessage(&msg); //no check
		::DispatchMessageW(&msg); //no check
	}
	//window
	::DestroyWindow(g_hCtrlWindow);  //no check
	g_hCtrlWindow = NULL;
	//clean up
	g_cleanup_proc(g_cleanup_param);
	return bRet == -1 ? 2 : 0;
}

void set_server_exit_event() noexcept
{
	_post_quit_message();
}

////////////////////////////////////////////////////////////////////////////////
